/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* $Id: PageSequenceLayoutManager.java,v 1.2 2007/09/19 04:59:29 lzy Exp $ */

package com.wisii.wisedoc.layoutmanager;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import com.wisii.wisedoc.area.AreaTreeHandler;
import com.wisii.wisedoc.area.Block;
import com.wisii.wisedoc.area.Footnote;
import com.wisii.wisedoc.area.LineArea;
import com.wisii.wisedoc.area.PageViewport;
import com.wisii.wisedoc.area.Resolvable;
import com.wisii.wisedoc.document.CellElement;
import com.wisii.wisedoc.document.Constants;
import com.wisii.wisedoc.document.Element;
import com.wisii.wisedoc.document.FOText;
import com.wisii.wisedoc.document.Flow;
import com.wisii.wisedoc.document.PageSequence;
import com.wisii.wisedoc.document.StaticContent;
import com.wisii.wisedoc.document.WiseDocDocument;
import com.wisii.wisedoc.document.attribute.Region;
import com.wisii.wisedoc.document.attribute.RegionBody;
import com.wisii.wisedoc.document.attribute.SideRegion;
import com.wisii.wisedoc.document.attribute.SimplePageMaster;
import com.wisii.wisedoc.document.datatype.Numeric;
import com.wisii.wisedoc.exception.FOVException;
import com.wisii.wisedoc.layoutmanager.PageBreakingAlgorithm.PageBreakingLayoutListener;
import com.wisii.wisedoc.layoutmanager.inline.ContentLayoutManager;
import com.wisii.wisedoc.log.LogUtil;
import com.wisii.wisedoc.traits.MinOptMax;

/**
 * LayoutManager for a PageSequence. This class is instantiated by
 * area.AreaTreeHandler for each fo:page-sequence found in the input document.
 */
public class PageSequenceLayoutManager extends AbstractLayoutManager {

	/**
	 * AreaTreeHandler which activates the PSLM and controls the rendering of
	 * its pages.
	 */
	private AreaTreeHandler areaTreeHandler;

	/**
	 * fo:page-sequence formatting object being processed by this class
	 */
	private PageSequence pageSeq;

	private PageProvider pageProvider;

	/**
	 * Current page with page-viewport-area being filled by the PSLM.
	 */
	private Page curPage = null;

	/**
	 * The FlowLayoutManager object, which processes the single fo:flow of the
	 * fo:page-sequence
	 */
	private FlowLayoutManager childFLM = null;

	private int startPageNum = 0;
	private int currentPageNum = 0;

	private Block separatorArea = null;

	/**
	 * Constructor
	 * 
	 * @param ath
	 *            the area tree handler object
	 * @param pseq
	 *            fo:page-sequence to process
	 */
	public PageSequenceLayoutManager(final AreaTreeHandler ath, final PageSequence pseq) {
		super(pseq);
		this.areaTreeHandler = ath;
		this.pageSeq = pseq;
		this.pageProvider = new PageProvider(this.pageSeq);
	}

	/**
	 * @see com.wisii.wisedoc.layoutmanager.LayoutManager
	 * @return the LayoutManagerMaker object
	 */
	public LayoutManagerMaker getLayoutManagerMaker() {
		return areaTreeHandler.getLayoutManagerMaker();
	}

	/** @return the PageProvider applicable to this page-sequence. */
	public PageProvider getPageProvider() {
		return this.pageProvider;
	}

	/**
	 * Activate the layout of this page sequence. PageViewports corresponding to
	 * each page generated by this page sequence will be created and sent to the
	 * AreaTreeModel for rendering.
	 */
	public void activateLayout() {
		/* 【添加：START】 by 李晓光 2008-09-23*/
		if(pageProvider != null) {
			pageProvider.cachedPages.clear();
		}
		/*pageSeq.releasePageSequence();*/
		/*clearBlockSource(pageSeq);*/
		/* 【添加：START】 by 李晓光 2008-09-23*/
		
		startPageNum = pageSeq.getStartingPageNumber();
		currentPageNum = startPageNum - 1;

		LineArea title = null;

		if (pageSeq.getTitleFO() != null) {
			try {
				final ContentLayoutManager clm = getLayoutManagerMaker()
						.makeContentLayoutManager(this, pageSeq.getTitleFO());
				title = (LineArea) clm.getParentArea(null);
			} catch (final IllegalStateException e) {
				// empty title; do nothing
			}
		}

		areaTreeHandler.getAreaTreeModel().startPageSequence(title);
		LogUtil.debug("Starting layout");

		curPage = makeNewPage(false, false);

		final Flow mainFlow = pageSeq.getMainFlow();

		childFLM = getLayoutManagerMaker()
				.makeFlowLayoutManager(this, mainFlow);

		//XXX 这里进行最终的排版，这里有个限制，就是单个单词的长度不能超过8页(A4纸大小)
		final PageBreaker breaker = new PageBreaker(this);
		final int flowBPD = getCurrentPV().getBodyRegion().getRemainingBPD();
		breaker.doLayout(flowBPD);

		finishPage();
	}
	public static void clearBlockSource(final CellElement ele){
		if(ele == null) {
			return;
		}
		if(ele instanceof com.wisii.wisedoc.document.Block) {
			((com.wisii.wisedoc.document.Block)ele).reset();
		} else if(ele instanceof FOText) {
			((FOText)ele).reset();
		}
		final List<CellElement> children = ele.getChildren(0);
		if(children == null || children.size() == 0) {
			return;
		}
		for (final CellElement cell : children) {
			clearBlockSource(cell);
		}
	}
	/**
	 * Finished the page-sequence and notifies everyone about it.
	 */
	public void finishPageSequence() {
		if (!pageSeq.getId().equals("")) {
			areaTreeHandler.signalIDProcessed(pageSeq.getId());
		}
		final WiseDocDocument doc = (WiseDocDocument)pageSeq.getParent();
		doc.notifyPageSequenceFinished(currentPageNum, (currentPageNum - startPageNum) + 1);
		areaTreeHandler.notifyPageSequenceFinished(pageSeq,
				(currentPageNum - startPageNum) + 1);
		/*pageSeq.releasePageSequence();*/
		LogUtil.debug("Ending layout");
	}

	private class PageBreaker extends AbstractBreaker {

		private PageSequenceLayoutManager pslm;
		private boolean firstPart = true;
		private boolean pageBreakHandled;
		private boolean needColumnBalancing;

		private StaticContentLayoutManager footnoteSeparatorLM = null;

		public PageBreaker(final PageSequenceLayoutManager pslm) {
			this.pslm = pslm;
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker */
		@Override
		protected void updateLayoutContext(final LayoutContext context) {
			final int flowIPD = getCurrentPV().getCurrentSpan().getColumnWidth();
			context.setRefIPD(flowIPD);
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker#getTopLevelLM() */
		@Override
		protected LayoutManager getTopLevelLM() {
			return pslm;
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker#getPageProvider() */
		@Override
		protected PageSequenceLayoutManager.PageProvider getPageProvider() {
			return pageProvider;
		}

		/**
		 * @see com.wisii.wisedoc.layoutmanager.AbstractBreaker#getLayoutListener()
		 */
		@Override
		protected PageBreakingLayoutListener getLayoutListener() {
			return new PageBreakingLayoutListener() {

				public void notifyOverflow(final int part, final Element obj) {
					final Page p = pageProvider.getPage(false, part,
							PageProvider.RELTO_CURRENT_ELEMENT_LIST);
					final RegionBody body = (RegionBody) p.getSimplePageMaster()
							.getRegion(Constants.FO_REGION_BODY);
					final String err = "越界溢出TODO";
						/*FONode
							.decorateWithContextInfo(
									"Content of the region-body on page "
											+ p.getPageViewport()
													.getPageNumberString()
											+ " overflows the available area in block-progression dimension.",
									obj);*/
					if (body.getOverflow() == Constants.EN_ERROR_IF_OVERFLOW) {
						throw new RuntimeException(err);
					} else {
						LogUtil.warn(err);
					}
				}

			};
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker */
		@Override
		protected int handleSpanChange(final LayoutContext childLC,
				int nextSequenceStartsOn) {
			needColumnBalancing = false;
			if (childLC.getNextSpan() != Constants.NOT_SET) {
				// Next block list will have a different span.
				nextSequenceStartsOn = childLC.getNextSpan();
				needColumnBalancing = (childLC.getNextSpan() == Constants.EN_ALL);
			}
			if (needColumnBalancing) {
				LogUtil.debug("Column balancing necessary for the next element list!!!");
			}
			return nextSequenceStartsOn;
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker */
		@Override
		protected int getNextBlockList(final LayoutContext childLC,
				final int nextSequenceStartsOn, final List blockLists) {
			if (!firstPart) {
				// if this is the first page that will be created by
				// the current BlockSequence, it could have a break
				// condition that must be satisfied;
				// otherwise, we may simply need a new page
				handleBreakTrait(nextSequenceStartsOn);
			}
			firstPart = false;
			pageBreakHandled = true;
			pageProvider.setStartOfNextElementList(currentPageNum,
					getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
			return super.getNextBlockList(childLC, nextSequenceStartsOn,
					blockLists);
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker */
		@Override
		protected LinkedList getNextKnuthElements(final LayoutContext context,
				final int alignment) {
			LinkedList contentList = null;

			while (!childFLM.isFinished() && contentList == null) {
				contentList = childFLM.getNextKnuthElements(context, alignment);
			}

			// scan contentList, searching for footnotes
			boolean bFootnotesPresent = false;
			if (contentList != null) {
				final ListIterator contentListIterator = contentList.listIterator();
				while (contentListIterator.hasNext()) {
					final ListElement element = (ListElement) contentListIterator
							.next();
					if (element instanceof KnuthBlockBox
							&& ((KnuthBlockBox) element).hasAnchors()) {
						// element represents a line with footnote citations
						bFootnotesPresent = true;
						final LayoutContext footnoteContext = new LayoutContext(
								context);
						footnoteContext.setStackLimit(context.getStackLimit());
						footnoteContext.setRefIPD(getCurrentPV()
								.getRegionReference(Constants.FO_REGION_BODY)
								.getIPD());
						final LinkedList footnoteBodyLMs = ((KnuthBlockBox) element)
								.getFootnoteBodyLMs();
						final ListIterator footnoteBodyIterator = footnoteBodyLMs
								.listIterator();
					}
				}
			}

			// handle the footnote separator
			StaticContent footnoteSeparator;
			if (bFootnotesPresent
					&& (footnoteSeparator = pageSeq
							.getStaticContent("xsl-footnote-separator")) != null) {
				// the footnote separator can contain page-dependent content
				// such as
				// page numbers or retrieve markers, so its areas cannot simply
				// be
				// obtained now and repeated in each page;
				// we need to know in advance the separator bpd: the actual
				// separator
				// could be different from page to page, but its bpd would
				// likely be
				// always the same

				// create a Block area that will contain the separator areas
				separatorArea = new Block();
				separatorArea.setIPD(pslm.getCurrentPV().getRegionReference(
						Constants.FO_REGION_BODY).getIPD());
				// create a StaticContentLM for the footnote separator
				footnoteSeparatorLM = getLayoutManagerMaker()
						.makeStaticContentLayoutManager(pslm,
								footnoteSeparator, separatorArea);
				footnoteSeparatorLM.doLayout();

				footnoteSeparatorLength = new MinOptMax(separatorArea.getBPD());
			}
			return contentList;
		}

		@Override
		protected int getCurrentDisplayAlign() {
			return curPage.getSimplePageMaster().getRegion(
					Constants.FO_REGION_BODY).getDisplayAlign();
		}

		@Override
		protected boolean hasMoreContent() {
			return !childFLM.isFinished();
		}

		@Override
		protected void addAreas(final PositionIterator posIter, final LayoutContext context) {
			if (footnoteSeparatorLM != null) {
				final StaticContent footnoteSeparator = pageSeq
						.getStaticContent("xsl-footnote-separator");
				// create a Block area that will contain the separator areas
				separatorArea = new Block();
				separatorArea.setIPD(getCurrentPV().getRegionReference(
						Constants.FO_REGION_BODY).getIPD());
				// create a StaticContentLM for the footnote separator
				footnoteSeparatorLM = getLayoutManagerMaker()
						.makeStaticContentLayoutManager(pslm,
								footnoteSeparator, separatorArea);
				footnoteSeparatorLM.doLayout();
			}

			childFLM.addAreas(posIter, context);
		}

		@Override
		protected void doPhase3(final PageBreakingAlgorithm alg, final int partCount,
				final BlockSequence originalList, final BlockSequence effectiveList) {
			if (needColumnBalancing) {
				doPhase3WithColumnBalancing(alg, partCount, originalList,
						effectiveList);
			} else {
				if (!hasMoreContent() && pageSeq.hasPagePositionLast()) {
					// last part is reached and we have a "last page" condition
					doPhase3WithLastPage(alg, partCount, originalList,
							effectiveList);
				} else {
					// Directly add areas after finding the breaks
					addAreas(alg, partCount, originalList, effectiveList);
				}
			}
		}

		private void doPhase3WithLastPage(final PageBreakingAlgorithm alg,
				final int partCount, final BlockSequence originalList,
				final BlockSequence effectiveList) {
			int newStartPos;
			final int restartPoint = pageProvider
					.getStartingPartIndexForLastPage(partCount);
			if (restartPoint > 0) {
				// Add definitive areas before last page
				addAreas(alg, restartPoint, originalList, effectiveList);
				// Get page break from which we restart
				final PageBreakPosition pbp = (PageBreakPosition) alg.getPageBreaks()
						.get(restartPoint - 1);
				newStartPos = pbp.getLeafPos();
				// Handle page break right here to avoid any side-effects
				if (newStartPos > 0) {
					handleBreakTrait(EN_PAGE);
				}
			} else {
				newStartPos = 0;
			}
			LogUtil.debug("Last page handling now!!!");
			LogUtil.debug("===================================================");
			LogUtil.debug("Restarting at " + restartPoint
					+ ", new start position: " + newStartPos);

			pageBreakHandled = true;
			// Update so the available BPD is reported correctly
			pageProvider.setStartOfNextElementList(currentPageNum,
					getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
			pageProvider.setLastPageIndex(currentPageNum);

			// Restart last page
			final PageBreakingAlgorithm algRestart = new PageBreakingAlgorithm(
					getTopLevelLM(), getPageProvider(), getLayoutListener(),
					alg.getAlignment(), alg.getAlignmentLast(),
					footnoteSeparatorLength, isPartOverflowRecoveryActivated(),
					false, false);
			// alg.setConstantLineWidth(flowBPD);
			final int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
					newStartPos, 1, true, BreakingAlgorithm.ALL_BREAKS);
			LogUtil.debug("restart: iOptPageCount= "
					+ iOptPageCount + " pageBreaks.size()= "
					+ algRestart.getPageBreaks().size());
			final boolean replaceLastPage = iOptPageCount <= getCurrentPV()
					.getBodyRegion().getColumnCount();
			if (replaceLastPage) {

				// Replace last page
				pslm.curPage = pageProvider.getPage(false, currentPageNum);
				// Make sure we only add the areas we haven't added already
				effectiveList.ignoreAtStart = newStartPos;
				addAreas(algRestart, iOptPageCount, originalList, effectiveList);
			} else {
				effectiveList.ignoreAtStart = newStartPos;
				addAreas(alg, restartPoint, partCount - restartPoint,
						originalList, effectiveList);
				// Add blank last page
				pageProvider.setLastPageIndex(currentPageNum + 1);
				pslm.curPage = makeNewPage(true, true);
			}
			LogUtil.debug("===================================================");
		}

		private void doPhase3WithColumnBalancing(final PageBreakingAlgorithm alg,
				final int partCount, final BlockSequence originalList,
				final BlockSequence effectiveList) {
			LogUtil.debug("Column balancing now!!!");
			LogUtil.debug("===================================================");
			int newStartPos;
			final int restartPoint = pageProvider
					.getStartingPartIndexForLastPage(partCount);
			if (restartPoint > 0) {
				// Add definitive areas
				addAreas(alg, restartPoint, originalList, effectiveList);
				// Get page break from which we restart
				final PageBreakPosition pbp = (PageBreakPosition) alg.getPageBreaks()
						.get(restartPoint - 1);
				newStartPos = pbp.getLeafPos();
				// Handle page break right here to avoid any side-effects
				if (newStartPos > 0) {
					handleBreakTrait(EN_PAGE);
				}
			} else {
				newStartPos = 0;
			}
			LogUtil.debug("Restarting at " + restartPoint
					+ ", new start position: " + newStartPos);

			pageBreakHandled = true;
			// Update so the available BPD is reported correctly
			pageProvider.setStartOfNextElementList(currentPageNum,
					getCurrentPV().getCurrentSpan().getCurrentFlowIndex());

			// Restart last page
			final PageBreakingAlgorithm algRestart = new BalancingColumnBreakingAlgorithm(
					getTopLevelLM(), getPageProvider(), getLayoutListener(),
					alignment, Constants.EN_START, footnoteSeparatorLength,
					isPartOverflowRecoveryActivated(), getCurrentPV()
							.getBodyRegion().getColumnCount());
			// alg.setConstantLineWidth(flowBPD);
			final int iOptPageCount = algRestart.findBreakingPoints(effectiveList,
					newStartPos, 1, true, BreakingAlgorithm.ALL_BREAKS);
			LogUtil.debug("restart: iOptPageCount= "
					+ iOptPageCount + " pageBreaks.size()= "
					+ algRestart.getPageBreaks().size());
			if (iOptPageCount > getCurrentPV().getBodyRegion().getColumnCount()) {
				LogUtil.warn("Breaking algorithm produced more columns than are available.");
				/*
				 * reenable when everything works throw new
				 * IllegalStateException(
				 * "Breaking algorithm must not produce more columns than available."
				 * );
				 */
			}
			// Make sure we only add the areas we haven't added already
			effectiveList.ignoreAtStart = newStartPos;
			addAreas(algRestart, iOptPageCount, originalList, effectiveList);
			LogUtil.debug("===================================================");
		}

		@Override
		protected void startPart(final BlockSequence list, final int breakClass) {
			LogUtil.debug("startPart() breakClass=" + breakClass);
			if (curPage == null) {
				throw new IllegalStateException("当前页不能为空");
			}
			if (!pageBreakHandled) {

				// firstPart is necessary because we need the first page before
				// we start the
				// algorithm so we have a BPD and IPD. This may subject to
				// change later when we
				// start handling more complex cases.
				if (!firstPart) {
					// if this is the first page that will be created by
					// the current BlockSequence, it could have a break
					// condition that must be satisfied;
					// otherwise, we may simply need a new page
					handleBreakTrait(breakClass);
				}
				pageProvider.setStartOfNextElementList(currentPageNum,
						getCurrentPV().getCurrentSpan().getCurrentFlowIndex());
			}
			pageBreakHandled = false;
			// add static areas and resolve any new id areas
			// finish page and add to area tree
			firstPart = false;
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker#handleEmptyContent() */
		@Override
		protected void handleEmptyContent() {
			getCurrentPV().getPage().fakeNonEmpty();
		}

		@Override
		protected void finishPart(final PageBreakingAlgorithm alg,
				final PageBreakPosition pbp) {
			// add footnote areas
			if (pbp.footnoteFirstListIndex < pbp.footnoteLastListIndex
					|| pbp.footnoteFirstElementIndex <= pbp.footnoteLastElementIndex) {
				// call addAreas() for each FootnoteBodyLM
				for (int i = pbp.footnoteFirstListIndex; i <= pbp.footnoteLastListIndex; i++) {
					final LinkedList elementList = alg.getFootnoteList(i);
					final int firstIndex = (i == pbp.footnoteFirstListIndex ? pbp.footnoteFirstElementIndex
							: 0);
					final int lastIndex = (i == pbp.footnoteLastListIndex ? pbp.footnoteLastElementIndex
							: elementList.size() - 1);

					SpaceResolver.performConditionalsNotification(elementList,
							firstIndex, lastIndex, -1);
					final LayoutContext childLC = new LayoutContext(0);
					AreaAdditionUtil.addAreas(null, new KnuthPossPosIter(
							elementList, firstIndex, lastIndex + 1), childLC);
				}
				// set the offset from the top margin
				final Footnote parentArea = getCurrentPV().getBodyRegion()
						.getFootnote();
				int topOffset = getCurrentPV().getBodyRegion().getBPD()
						- parentArea.getBPD();
				if (separatorArea != null) {
					topOffset -= separatorArea.getBPD();
				}
				parentArea.setTop(topOffset);
				parentArea.setSeparator(separatorArea);
			}
			getCurrentPV().getCurrentSpan().notifyFlowsFinished();
		}

		@Override
		protected LayoutManager getCurrentChildLM() {
			return childFLM;
		}

		/** @see com.wisii.wisedoc.layoutmanager.AbstractBreaker#observeElementList(java.util.List) */
		@Override
		protected void observeElementList(final List elementList) {
			ElementListObserver.observe(elementList, "breaker",
					((PageSequence) pslm.getFObj()).getId());
		}

	}

	/**
	 * Provides access to the current page.
	 * 
	 * @return the current Page
	 */
	@Override
	public Page getCurrentPage() {
		return curPage;
	}

	/**
	 * Provides access to the current page viewport.
	 * 
	 * @return the current PageViewport
	 */
	/*
	 * public PageViewport getCurrentPageViewport() { return
	 * curPage.getPageViewport(); }
	 */

	/**
	 * Provides access to this object
	 * 
	 * @return this PageSequenceLayoutManager instance
	 */
	@Override
	public PageSequenceLayoutManager getPSLM() {
		return this;
	}

	/**
	 * This returns the first PageViewport that contains an id trait matching
	 * the idref argument, or null if no such PV exists.
	 * 
	 * @param idref
	 *            the idref trait needing to be resolved
	 * @return the first PageViewport that contains the ID trait
	 */
	public PageViewport getFirstPVWithID(final String idref) {
		final List list = areaTreeHandler.getPageViewportsContainingID(idref);
		if (list != null && list.size() > 0) {
			return (PageViewport) list.get(0);
		}
		return null;
	}

	/**
	 * This returns the last PageViewport that contains an id trait matching the
	 * idref argument, or null if no such PV exists.
	 * 
	 * @param idref
	 *            the idref trait needing to be resolved
	 * @return the last PageViewport that contains the ID trait
	 */
	public PageViewport getLastPVWithID(final String idref) {
		final List list = areaTreeHandler.getPageViewportsContainingID(idref);
		if (list != null && list.size() > 0) {
			return (PageViewport) list.get(list.size() - 1);
		}
		return null;
	}

	/**
	 * Add an ID reference to the current page. When adding areas the area adds
	 * its ID reference. For the page layout manager it adds the id reference
	 * with the current page to the area tree.
	 * 
	 * @param id
	 *            the ID reference to add
	 */
	public void addIDToPage(final String id) {
		if (id != null && id.length() > 0) {
			areaTreeHandler.associateIDWithPageViewport(id, curPage
					.getPageViewport());
		}
	}

	/**
	 * Add an id reference of the layout manager in the AreaTreeHandler, if the
	 * id hasn't been resolved yet
	 * 
	 * @param id
	 *            the id to track
	 * @return a boolean indicating if the id has already been resolved TODO
	 *         Maybe give this a better name
	 */
	public boolean associateLayoutManagerID(final String id) {
		LogUtil.debug("associateLayoutManagerID(" + id + ")");
		if (!areaTreeHandler.alreadyResolvedID(id)) {
			areaTreeHandler.signalPendingID(id);
			return false;
		} else {
			return true;
		}
	}

	/**
	 * Notify the areaTreeHandler that the LayoutManagers containing idrefs have
	 * finished creating areas
	 * 
	 * @param id
	 *            the id for which layout has finished
	 */
	public void notifyEndOfLayout(final String id) {
		areaTreeHandler.signalIDProcessed(id);
	}

	/**
	 * Identify an unresolved area (one needing an idref to be resolved, e.g.
	 * the internal-destination of an fo:basic-link) for both the
	 * AreaTreeHandler and PageViewport object.
	 * 
	 * The AreaTreeHandler keeps a document-wide list of idref's and the PV's
	 * needing them to be resolved. It uses this to send notifications to the
	 * PV's when an id has been resolved.
	 * 
	 * The PageViewport keeps lists of id's needing resolving, along with the
	 * child areas (page-number-citation, basic-link, etc.) of the PV needing
	 * their resolution.
	 * 
	 * @param id
	 *            the ID reference to add
	 * @param res
	 *            the resolvable object that needs resolving
	 */
	public void addUnresolvedArea(final String id, final Resolvable res) {
		curPage.getPageViewport().addUnresolvedIDRef(id, res);
		areaTreeHandler.addUnresolvedIDRef(id, curPage.getPageViewport());
	}

	/**
	 * Bind the RetrieveMarker to the corresponding Marker subtree. If the
	 * boundary is page then it will only check the current page. For
	 * page-sequence and document it will lookup preceding pages from the area
	 * tree and try to find a marker. If we retrieve a marker from a preceding
	 * page, then the containing page does not have a qualifying area, and all
	 * qualifying areas have ended. Therefore we use last-ending-within-page
	 * (Constants.EN_LEWP) as the position.
	 * 
	 * @param rm
	 *            the RetrieveMarker instance whose properties are to used to
	 *            find the matching Marker.
	 * @return a bound RetrieveMarker instance, or null if no Marker could be
	 *         found.
	 */
	/*public RetrieveMarker resolveRetrieveMarker(RetrieveMarker rm) {
		AreaTreeModel areaTreeModel = areaTreeHandler.getAreaTreeModel();
		String name = rm.getRetrieveClassName();
		int pos = rm.getRetrievePosition();
		int boundary = rm.getRetrieveBoundary();

		// get marker from the current markers on area tree
		Marker mark = (Marker) getCurrentPV().getMarker(name, pos);
		if (mark == null && boundary != EN_PAGE) {
			// go back over pages until mark found
			// if document boundary then keep going
			boolean doc = boundary == EN_DOCUMENT;
			int seq = areaTreeModel.getPageSequenceCount();
			int page = areaTreeModel.getPageCount(seq) - 1;
			while (page < 0 && doc && seq > 1) {
				seq--;
				page = areaTreeModel.getPageCount(seq) - 1;
			}
			while (page >= 0) {
				PageViewport pv = areaTreeModel.getPage(seq, page);
				mark = (Marker) pv.getMarker(name, Constants.EN_LEWP);
				if (mark != null) {
					break;
				}
				page--;
				if (page < 0 && doc && seq > 1) {
					seq--;
					page = areaTreeModel.getPageCount(seq) - 1;
				}
			}
		}

		if (mark == null) {
			LogUtil.debug("found no marker with name: " + name);
			return null;
		} else {
			rm.bindMarker(mark);
			return rm;
		}
	}*/

	private Page makeNewPage(final boolean bIsBlank, final boolean bIsLast) {
		if (curPage != null) {
			finishPage();
		}

		currentPageNum++;

		curPage = pageProvider.getPage(bIsBlank, currentPageNum,
				PageProvider.RELTO_PAGE_SEQUENCE);

		LogUtil.debug("[" + curPage.getPageViewport().getPageNumberString()
				+ (bIsBlank ? "*" : "") + "]");

		addIDToPage(pageSeq.getId());
		return curPage;
	}

	private void layoutSideRegion(final int regionID) {
		final SideRegion reg = (SideRegion) curPage.getSimplePageMaster().getRegion(
				regionID);
		if (reg == null) {
			return;
		}
		final StaticContent sc = pageSeq.getStaticContent(reg.getRegionName());
		if (sc == null) {
			return;
		}

		final StaticContentLayoutManager lm = getLayoutManagerMaker()
				.makeStaticContentLayoutManager(this, sc, reg);
		lm.doLayout();
	}

	private void finishPage() {
		curPage.getPageViewport().dumpMarkers();
		// Layout side regions
		layoutSideRegion(FO_REGION_BEFORE);
		layoutSideRegion(FO_REGION_AFTER);
		layoutSideRegion(FO_REGION_START);
		layoutSideRegion(FO_REGION_END);

		// Try to resolve any unresolved IDs for the current page.
		//
		areaTreeHandler.tryIDResolution(curPage.getPageViewport());
		// Queue for ID resolution and rendering
		areaTreeHandler.getAreaTreeModel().addPage(curPage.getPageViewport());
		LogUtil.debug("page finished: "
				+ curPage.getPageViewport().getPageNumberString()
				+ ", current num: " + currentPageNum);
		curPage = null;
	}

	/**
	 * Depending on the kind of break condition, move to next column or page.
	 * May need to make an empty page if next page would not have the desired
	 * "handedness".
	 * 
	 * @param breakVal
	 *            - value of break-before or break-after trait.
	 */
	private void handleBreakTrait(final int breakVal) {
		if (breakVal == Constants.EN_ALL) {
			// break due to span change in multi-column layout
			curPage.getPageViewport().createSpan(true);
			return;
		} else if (breakVal == Constants.EN_NONE) {
			curPage.getPageViewport().createSpan(false);
			return;
		} else if (breakVal == Constants.EN_COLUMN || breakVal <= 0) {
			final PageViewport pv = curPage.getPageViewport();

			// Check if previous page was spanned
			boolean forceNewPageWithSpan = false;
			final RegionBody rb = (RegionBody) curPage.getSimplePageMaster()
					.getRegion(Constants.FO_REGION_BODY);
			if (breakVal < 0 && rb.getColumnCount() > 1
					&& pv.getCurrentSpan().getColumnCount() == 1) {
				forceNewPageWithSpan = true;
			}

			if (forceNewPageWithSpan) {
				curPage = makeNewPage(false, false);
				curPage.getPageViewport().createSpan(true);
			} else if (pv.getCurrentSpan().hasMoreFlows()) {
				pv.getCurrentSpan().moveToNextFlow();
			} else {
				curPage = makeNewPage(false, false);
			}
			return;
		}
		LogUtil.debug("handling break-before after page " + currentPageNum
				+ " breakVal=" + breakVal);
		if (needBlankPageBeforeNew(breakVal)) {
			curPage = makeNewPage(true, false);
		}
		if (needNewPage(breakVal)) {
			curPage = makeNewPage(false, false);
		}
	}

	/**
	 * Check if a blank page is needed to accomodate desired even or odd page
	 * number.
	 * 
	 * @param breakVal
	 *            - value of break-before or break-after trait.
	 */
	private boolean needBlankPageBeforeNew(final int breakVal) {
		if (breakVal == Constants.EN_PAGE
				|| (curPage.getPageViewport().getPage().isEmpty())) {
			// any page is OK or we already have an empty page
			return false;
		} else {
			/* IF we are on the kind of page we need, we'll need a new page. */
			// if (currentPageNum % 2 == 0)
			if ((currentPageNum & 1) == 0) { // even page
				return (breakVal == Constants.EN_EVEN_PAGE);
			} else { // odd page
				return (breakVal == Constants.EN_ODD_PAGE);
			}
		}
	}

	/**
	 * See if need to generate a new page
	 * 
	 * @param breakVal
	 *            - value of break-before or break-after trait.
	 */
	private boolean needNewPage(final int breakVal) {
		if (curPage.getPageViewport().getPage().isEmpty()) {
			if (breakVal == Constants.EN_PAGE) {
				return false;
			}
			// else if (currentPageNum % 2 == 0)
			else if ((currentPageNum & 1) == 0) { // even page
				return (breakVal == Constants.EN_ODD_PAGE);
			} else { // odd page
				return (breakVal == Constants.EN_EVEN_PAGE);
			}
		} else {
			return true;
		}
	}

	/**
	 * <p>
	 * This class delivers Page instances. It also caches them as necessary.
	 * </p>
	 * <p>
	 * Additional functionality makes sure that surplus instances that are
	 * requested by the page breaker are properly discarded, especially in
	 * situations where hard breaks cause blank pages. The reason for that: The
	 * page breaker sometimes needs to preallocate additional pages since it
	 * doesn't know exactly until the end how many pages it really needs.
	 * </p>
	 */
	public class PageProvider {

		/**
		 * Indices are evaluated relative to the first page in the
		 * page-sequence.
		 */
		public static final int RELTO_PAGE_SEQUENCE = 0;
		/**
		 * Indices are evaluated relative to the first page in the current
		 * element list.
		 */
		public static final int RELTO_CURRENT_ELEMENT_LIST = 1;

		private int startPageOfPageSequence;
		private int startPageOfCurrentElementList;
		private int startColumnOfCurrentElementList;
		private List cachedPages = new java.util.ArrayList();

		private int lastPageIndex = -1;
		private int indexOfCachedLastPage = -1;

		// Cache to optimize getAvailableBPD() calls
		private int lastRequestedIndex = -1;
		private int lastReportedBPD = -1;

		/**
		 * Main constructor.
		 * 
		 * @param ps
		 *            The page-sequence the provider operates on
		 */
		public PageProvider(final PageSequence ps) {
			this.startPageOfPageSequence = ps.getStartingPageNumber();
		}

		/**
		 * The page breaker notifies the provider about the page number an
		 * element list starts on so it can later retrieve PageViewports
		 * relative to this first page.
		 * 
		 * @param startPage
		 *            the number of the first page for the element list.
		 * @param startColumn
		 *            the starting column number for the element list.
		 */
		public void setStartOfNextElementList(final int startPage, final int startColumn) {
			LogUtil.debug("start of the next element list is:" + " page="
					+ startPage + " col=" + startColumn);
			this.startPageOfCurrentElementList = startPage
					- startPageOfPageSequence + 1;
			this.startColumnOfCurrentElementList = startColumn;
			// Reset Cache
			this.lastRequestedIndex = -1;
			this.lastReportedBPD = -1;
		}

		/**
		 * Sets the index of the last page. This is done as soon as the position
		 * of the last page is known or assumed.
		 * 
		 * @param index
		 *            the index relative to the first page in the page-sequence
		 */
		public void setLastPageIndex(final int index) {
			this.lastPageIndex = index;
		}

		/**
		 * Returns the available BPD for the part/page indicated by the index
		 * parameter. The index is the part/page relative to the start of the
		 * current element list. This method takes multiple columns into
		 * account.
		 * 
		 * @param index
		 *            zero-based index of the requested part/page
		 * @return the available BPD
		 */
		public int getAvailableBPD(final int index) {
			// Special optimization: There may be many equal calls by the
			// BreakingAlgorithm
			if (this.lastRequestedIndex == index) {
				LogUtil.debug("getAvailableBPD(" + index + ") -> (cached) "
						+ lastReportedBPD);
				return this.lastReportedBPD;
			}
			int c = index;
			int pageIndex = 0;
			int colIndex = startColumnOfCurrentElementList;
			Page page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
			while (c > 0) {
				colIndex++;
				if (colIndex >= page.getPageViewport().getCurrentSpan()
						.getColumnCount()) {
					colIndex = 0;
					pageIndex++;
					page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
				}
				c--;
			}
			this.lastRequestedIndex = index;
			this.lastReportedBPD = page.getPageViewport().getBodyRegion()
					.getRemainingBPD();
			LogUtil.debug("getAvailableBPD(" + index + ") -> "
					+ lastReportedBPD);
			return this.lastReportedBPD;
		}

		/**
		 * Returns the part index (0<x<partCount) which denotes the first part
		 * on the last page generated by the current element list.
		 * 
		 * @param partCount
		 *            Number of parts determined by the breaking algorithm
		 * @return the requested part index
		 */
		public int getStartingPartIndexForLastPage(final int partCount) {
			int result = 0;
			int idx = 0;
			int pageIndex = 0;
			int colIndex = startColumnOfCurrentElementList;
			Page page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
			while (idx < partCount) {
				if ((colIndex >= page.getPageViewport().getCurrentSpan()
						.getColumnCount())) {
					colIndex = 0;
					pageIndex++;
					page = getPage(false, pageIndex, RELTO_CURRENT_ELEMENT_LIST);
					result = idx;
				}
				colIndex++;
				idx++;
			}
			return result;
		}

		/**
		 * Returns a Page.
		 * 
		 * @param isBlank
		 *            true if this page is supposed to be blank.
		 * @param index
		 *            Index of the page (see relativeTo)
		 * @param relativeTo
		 *            Defines which value the index parameter should be
		 *            evaluated relative to. (One of PageProvider.RELTO_*)
		 * @return the requested Page
		 */
		public Page getPage(final boolean isBlank, final int index, final int relativeTo) {
			if (relativeTo == RELTO_PAGE_SEQUENCE) {
				return getPage(isBlank, index);
			} else if (relativeTo == RELTO_CURRENT_ELEMENT_LIST) {
				int effIndex = startPageOfCurrentElementList + index;
				effIndex += startPageOfPageSequence - 1;
				return getPage(isBlank, effIndex);
			} else {
				throw new IllegalArgumentException("relativeTo: " + relativeTo
						+ "传入一个了非法的值");
			}
		}

		private Page getPage(final boolean isBlank, final int index) {
			final boolean isLastPage = (lastPageIndex >= 0)
					&& (index == lastPageIndex);
			LogUtil.debug("getPage(" + index + " "
					+ (isBlank ? "blank" : "non-blank")
					+ (isLastPage ? " <LAST>" : "") + ")");
			final int intIndex = index - startPageOfPageSequence;
			if (isBlank) {
				LogUtil.debug("blank page requested: " + index);
			}
			if (isLastPage) {
				LogUtil.debug("last page requested: " + index);
			}
			while (intIndex >= cachedPages.size()) {
				LogUtil.debug("Caching " + index);
				cacheNextPage(index, isBlank, isLastPage);
			}
			Page page = (Page) cachedPages.get(intIndex);
			boolean replace = false;
			if (page.getPageViewport().isBlank() != isBlank) {
				LogUtil.debug("blank condition doesn't match. Replacing PageViewport.");
				replace = true;
			}
			if ((isLastPage && indexOfCachedLastPage != intIndex)
					|| (!isLastPage && indexOfCachedLastPage >= 0)) {
				LogUtil.debug("last page condition doesn't match. Replacing PageViewport.");
				replace = true;
				indexOfCachedLastPage = (isLastPage ? intIndex : -1);
			}
			if (replace) {
				disardCacheStartingWith(intIndex);
				page = cacheNextPage(index, isBlank, isLastPage);
			}
			return page;
		}

		private void disardCacheStartingWith(final int index) {
			while (index < cachedPages.size()) {
				this.cachedPages.remove(cachedPages.size() - 1);
				if (!pageSeq.goToPreviousSimplePageMaster()) {
					LogUtil.warn("goToPreviousSimplePageMaster() on the first page called!");
				}
			}
		}

		private Page cacheNextPage(final int index, final boolean isBlank,
				final boolean isLastPage) {
			try {
				final String pageNumberString = pageSeq
						.makeFormattedPageNumber(index);
				final SimplePageMaster spm = pageSeq
						.getNextSimplePageMaster(index,
								(startPageOfPageSequence == index), isLastPage,
								isBlank);

				final Region body = spm.getRegion(FO_REGION_BODY);
				if (!pageSeq.getMainFlow().getFlowName().equals(
						body.getRegionName())) {
					// this is fine by the XSL Rec (fo:flow's flow-name can be
					// mapped to
					// any region), but we don't support it yet.
					throw new FOVException(
							"Flow '"
									+ pageSeq.getMainFlow().getFlowName()
									+ "' does not map to the region-body in page-master '"
									+ spm.getMasterName()
									+ "'.  FOV presently "
									+ "does not support this.");
				}
				final Page page = new Page(spm, index, pageNumberString, isBlank);
				// Set unique key obtained from the AreaTreeHandler
				page.getPageViewport().setKey(
						areaTreeHandler.generatePageViewportKey());
				//TODO 【修改】 by 李晓光 2008-08-29
				/*page.getPageViewport().setForeignAttributes(
						spm.getForeignAttributes());*/
				cachedPages.add(page);
				return page;
			} catch (final FOVException e) {
				// TODO Maybe improve. It'll mean to propagate this exception up
				// several
				// methods calls.
				throw new IllegalStateException(e.getMessage());
			}
		}

	}

	/**
	 * Act upon the force-page-count trait, in relation to the
	 * initial-page-number trait of the following page-sequence.
	 * 
	 * @param nextPageSeqInitialPageNumber
	 *            initial-page-number trait of next page-sequence
	 */
	public void doForcePageCount(final Numeric nextPageSeqInitialPageNumber) {

		int forcePageCount = pageSeq.getForcePageCount();

		// xsl-spec version 1.0 (15.oct 2001)
		// auto | even | odd | end-on-even | end-on-odd | no-force | inherit
		// auto:
		// Force the last page in this page-sequence to be an odd-page
		// if the initial-page-number of the next page-sequence is even.
		// Force it to be an even-page
		// if the initial-page-number of the next page-sequence is odd.
		// If there is no next page-sequence
		// or if the value of its initial-page-number is "auto" do not force any
		// page.

		// if force-page-count is auto then set the value of forcePageCount
		// depending on the initial-page-number of the next page-sequence
		if (nextPageSeqInitialPageNumber != null
				&& forcePageCount == Constants.EN_AUTO) {
			if (nextPageSeqInitialPageNumber.getEnum() != 0) {
				// auto | auto-odd | auto-even
				final int nextPageSeqPageNumberType = nextPageSeqInitialPageNumber
						.getEnum();
				if (nextPageSeqPageNumberType == Constants.EN_AUTO_ODD) {
					forcePageCount = Constants.EN_END_ON_EVEN;
				} else if (nextPageSeqPageNumberType == Constants.EN_AUTO_EVEN) {
					forcePageCount = Constants.EN_END_ON_ODD;
				} else { // auto
					forcePageCount = Constants.EN_NO_FORCE;
				}
			} else { // <integer> for explicit page number
				int nextPageSeqPageStart = nextPageSeqInitialPageNumber
						.getValue();
				// spec rule
				nextPageSeqPageStart = (nextPageSeqPageStart > 0) ? nextPageSeqPageStart
						: 1;
				// if (nextPageSeqPageStart % 2 == 0)
				if ((nextPageSeqPageStart & 1) == 0) { // explicit even
					// startnumber
					forcePageCount = Constants.EN_END_ON_ODD;
				} else { // explicit odd startnumber
					forcePageCount = Constants.EN_END_ON_EVEN;
				}
			}
		}

		if (forcePageCount == Constants.EN_EVEN) {
			// if ((currentPageNum - startPageNum + 1) % 2 != 0)
			if (((currentPageNum - startPageNum + 1) & 1) != 0) { // we have a
				// odd
				// number of
				// pages
				curPage = makeNewPage(true, false);
			}
		} else if (forcePageCount == Constants.EN_ODD) {
			// if ((currentPageNum - startPageNum + 1) % 2 == 0)
			if (((currentPageNum - startPageNum + 1) & 1) == 0) { // we have a
				// even
				// number of
				// pages
				curPage = makeNewPage(true, false);
			}
		} else if (forcePageCount == Constants.EN_END_ON_EVEN) {
			// if (currentPageNum % 2 != 0)
			if ((currentPageNum & 1) != 0) { // we are now on a odd page
				curPage = makeNewPage(true, false);
			}
		} else if (forcePageCount == Constants.EN_END_ON_ODD) {
			// if (currentPageNum % 2 == 0)
			if ((currentPageNum & 1) == 0) { // we are now on a even page
				curPage = makeNewPage(true, false);
			}
		} else if (forcePageCount == Constants.EN_NO_FORCE) {
			// i hope: nothing special at all
		}

		if (curPage != null) {
			finishPage();
		}
	}
	
	public static void main(final String[] args) {
		final WiseDocDocument doc = new WiseDocDocument();
		
	}
}
